package com.rankeiot.core.excel;

import ch.rabanti.nanoxlsx4j.Cell;
import ch.rabanti.nanoxlsx4j.ImportOptions;
import ch.rabanti.nanoxlsx4j.Workbook;
import ch.rabanti.nanoxlsx4j.Worksheet;
import ch.rabanti.nanoxlsx4j.exceptions.IOException;
import ch.rabanti.nanoxlsx4j.lowLevel.XlsxReader;
import ch.rabanti.nanoxlsx4j.styles.Style;
import com.jfinal.template.Engine;
import com.jfinal.template.Template;
import com.rankeiot.core.data.ValHolder;
import com.rankeiot.core.sqltoy.StreamDataSource;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 可根据模板导出
 * 模板中#(xx)表示取ds中变量xx,#[xx]表示循环取ds中xx列表中的值
 * 其中 列表数据类型为，数组，List,StreamDataSource
 * StreamDataSource可直接绑定sql,次数
 * 当导出的数据量较大时，可以用StreamDataSource,减少内存溢出风险
 */
public class XlsxTemplate {
    public static void render(InputStream template, Map<String, Object> dataSource, OutputStream output) throws IOException, java.io.IOException {
        XlsxReader xr = new XlsxReader(template, new ImportOptions());
        xr.read();
        Workbook wb = xr.getWorkbook();
        List<Worksheet> sheets = wb.getWorksheets();
        XlsxStreamWriter xsw = new XlsxStreamWriter(output, wb);
        Engine engine = new Engine();
        for (Worksheet sheet : sheets) {
            XlsxStreamWriter.SheetWriter sw = xsw.createSheet(sheet);
            ValHolder<Integer> rowNumber = new ValHolder<>(0);
            for (int i = 0; i <= sheet.getLastRowNumber(); i++) {
                //sw.writeRowCells(sheet.getRow(i));
                processRows(sheet.getRow(i), dataSource, sw, engine, rowNumber);
            }
            sw.end();
        }
        xsw.close();

    }

    public static ResponseEntity<StreamingResponseBody> render(InputStream template, Map<String, Object> dataSource, String fileName) {

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.parseMediaType("application/vnd.ms-excel"));
        // headers.set();
        try {
            headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
                .body(out -> {
                    try {
                        render(template, dataSource, out);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                });
    }

    private static void processRows(List<Cell> templateRow, Map<String, Object> dataSource, XlsxStreamWriter.SheetWriter sw, Engine engine, ValHolder<Integer> rowNumber) throws java.io.IOException {
        if (templateRow.size() == 0) {
            return;
        }
        //$,#
        boolean hasList = false;
        List<Cell> targetSells = new ArrayList<>();
        Set<String> listParams = new HashSet<>();
        for (Cell cell : templateRow) {
            Cell newCell = copyOf(cell);
            Object v = cell.getValue();
            if (v != null) {
                String vstr = v.toString();
                if (vstr.startsWith("#(") && vstr.endsWith(")")) {
                    Template template = engine.getTemplateByString(vstr, false);
                    newCell.setValue(template.renderToString(dataSource));
                } else if (vstr.startsWith("#[") && vstr.endsWith("]")) {
                    //hasList = true;
                    String name = vstr.substring(2, vstr.length() - 1);
                    //#(id+id2.aa)
                    listParams.addAll(matchName(name));
                    newCell.setValue("#(" + name + ")");
                }
            } else {
                newCell.setValue("");
            }
            targetSells.add(newCell);
        }
        if (listParams.size() > 0) {//按列表处理
            //合并列表数据
            Map<String, StreamDataSource> datas = new HashMap<>();
            Map<String, Object> commData = new HashMap<>();
            for (Map.Entry<String, Object> e : dataSource.entrySet()) {
                String name = e.getKey();
                Object v = e.getValue();
                if (v != null) {
                    if (v instanceof StreamDataSource) {
                        if (listParams.contains(name)) {
                            datas.put(name, (StreamDataSource) v);
                        }
                    } else if (v instanceof Collection) {
                        if (listParams.contains(name)) {
                            datas.put(name, new StreamDataSource((Collection) v));
                        }
                    } else if (v.getClass().isArray()) {
                        if (listParams.contains(name)) {
                            datas.put(name, new StreamDataSource((Object[]) v));
                        }
                    }
                    continue;
                }
                commData.put(name, v);
            }
            StreamDataSource<Map<String, Object>> target = StreamDataSource.merge(datas);
            target.foreach((data) -> {
                Map<String, Object> currentData = new HashMap<>();
                currentData.putAll(commData);
                currentData.putAll(data);

                List<Cell> rowCells = new ArrayList<>();
                for (Cell cell : targetSells) {
                    Cell newCell = copyOf(cell);
                    newCell.setRowNumber(rowNumber.value);
                    Object v = cell.getValue();
                    if (v != null) {
                        String vstr = v.toString();
                        if (vstr.startsWith("#(") && vstr.endsWith(")")) {
                            try {
                                Template template = engine.getTemplateByString(vstr, false);
                                newCell.setValue(template.renderToString(currentData));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    rowCells.add(newCell);
                }
                try {
                    instrEmptyCells(rowCells);
                    sw.writeRowCells(rowCells);
                    rowNumber.value++;
                } catch (java.io.IOException e) {
                    throw new RuntimeException(e);
                }
            });

        } else {//直接复制
            instrEmptyCells(targetSells);
            sw.writeRowCells(targetSells);
            rowNumber.value++;
        }
    }

    private static Cell copyOf(Cell cell) {
        Cell dist = new Cell(cell.getValue(), cell.getDataType());
        Style style = cell.getCellStyle();
        if (style != null) {
            dist.setStyle(style);
        }
        dist.setColumnNumber(cell.getColumnNumber());
        if (cell.getCellAddressType() != null) {
            dist.setCellAddressType(cell.getCellAddressType());
        }
        if (cell.getCellAddress() != null) {
            dist.setCellAddress(cell.getCellAddress());
        }
        Cell.CellType ct=cell.getDataType();
        if(ct!=null){
            dist.setDataType(ct);
        }
        return dist;
    }

    private static Pattern p = Pattern.compile("(^|\\(|\\s|\\+|\\-|\\*|\\/|\\?|\\:|\\^|\\&|\\|)+?(\\w+)(\\)|\\.|$|\\s|\\+|\\-|\\*|\\/|\\?|\\:|\\^|\\&|\\|)");

    private static Set<String> matchName(String express) {
        Matcher m = p.matcher(express);
        Set<String> ret = new HashSet<>();
        while (m.find()) {
            ret.add(m.group(2));
        }
        return ret;
    }

    private static void instrEmptyCells(List<Cell> cells) {
        Cell first = cells.get(0);
        for (int i = 0; i < first.getColumnNumber(); i++) {
            cells.add(0, new Cell());
        }
    }
}
